/**
 * Copyright @2019 Josin All Rights Reserved.
 * Author: Josin
 * Email : xeapplee@gmail.com
 */

#include <ck_task.h>
#include <http_request.h>

#pragma clang diagnostic push
#pragma ide diagnostic ignored   "OCUnusedGlobalDeclarationInspection"
#pragma clang diagnostic ignored "-Wmissing-noreturn"

/**
 * @brief Introduction
 * This thread works for HTTP UI events
 */
void http_callback(int fd, int signo, int event_fd)
{
    int           cfd;
    long          blen, n;
    char         *buff, *r;
    HTTP_REQUEST *req;
    EXJSON       *res, *rve, *bck;
    EXJSON_V     *vv, *vt;
    
    bck = NULL;
    
    if ( fd == server_fd )
    {
        cfd = socket_accept( fd, NULL, NULL );
        event_add(event_fd, cfd);
    }
    else
    {
        cfd = fd;
        
        blen = 0;
        buff = socket_read(cfd, &blen);
        
        req  = http_stream_parse( buff, ( unsigned long )blen, ( unsigned long * )&n );
        e_memfree(buff);
        if ( *(req->uri) == '/' && *(req->uri + 1) == '\0' )
        {
            write(cfd, E_STRL("HTTP/1.1 200 OK\r\nContent-Length:422\r\nContent-Type: application/json;charset=utf-8\r\nConnection:close\r\n\r\n"
                              "{\"message\":\"Welcome use cknit\",\"code\":\"ok\",\"version\":\"1.0\",\"port\":9898,\"APIs\":"
                              "[{\"name\":\"Get all monitors tasks\",\"method\":\"GET\",\"protocol\":\"HTTP/1.1\",\"url\": "
                              "\"http://127.0.0.1:9898/monitors\"},{\"name\":\"Add one monitors tasks\",\"method\":\"POST\","
                              "\"protocol\":\"HTTP/1.1\",\"url\":\"http://127.0.0.1:9898/monitors\"},"
                              "{\"name\": \"Modify one monitors tasks\", \"method\": \"PUT\", \"protocol\": "
                              "\"HTTP/1.1\", \"url\": \"http://127.0.0.1:9898/monitors\"}]}"));
        }
        else
        if ( e_memcmp(req->uri, E_STRL("/monitors")) == 0 && strlen(req->uri) == 9 )
        {
            if ( req->method_num == HTTP_GET )
            {
/**
 * @brief Introduction
 * if to get the Task list, response it with the JSON response.
 */
                buff = encode_json(tasks);
                r = malloc( sizeof(char) * ( 103 + strlen(buff) ) );
                e_memzero(r, sizeof(char) * ( 103 + strlen(buff) ) );
                sprintf(r, "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Type: application/json;charset=utf-8\r\n"
                           "Content-Length: %ld\r\n\r\n%s", strlen(buff), buff);
                write(cfd, r, sizeof( char) * strlen(r));
                e_memfree(r);
            }
            else
            if ( req->method_num == HTTP_POST )
            {
/**
 * @brief Introduction
 * When JSON POST to add Task, do the add job
 */
                res = decode_json(req->body);
                if ( !res ) {
                    write(cfd, E_STRL("HTTP/1.1 424 Validation Error\r\nContent-Length:49\r\nContent-Type: application/json;charset=UTF-8"
                                      "\r\nConnection: close\r\n\r\n{\"message\":\"Post body need JSON.\",\"code\":\"false\"}"));
                    goto trash_close;
                }
                r   = exjson_get_val_from_key(res, COMMAND);
                if ( !r || *r == '\0' ) {
                    write(cfd, E_STRL("HTTP/1.1 424 Validation Error\r\nContent-Length:54\r\nContent-Type: application/json;charset=UTF-8"
                                      "\r\nConnection: close\r\n\r\n{\"message\":\"Invalid command parameter\",\"code\":\"false\"}"));
                    goto trash_close;
                }
                r   = exjson_get_val_from_key(res, PERIOD);
                if ( !r || *r == '\0' ) {
                    write(cfd, E_STRL("HTTP/1.1 424 Validation Error\r\nContent-Length:53\r\nContent-Type: application/json;charset=UTF-8"
                                      "\r\nConnection: close\r\n\r\n{\"message\":\"Invalid period parameter\",\"code\":\"false\"}"));
                    goto trash_close;
                }
                
                add_object_int(res, "id", E_NUM_P(tasks) + 1);
                add_array_object(tasks, res);
    
                write(cfd, E_STRL("HTTP/1.1 200 OK\r\nContent-Length:58\r\nContent-Type: application/json;charset=UTF-8\r\n"
                                  "Connection: close\r\n\r\n{\"message\":\"Success\",\"code\":\"true\",\"operation\":\"Add task\"}"));
            }
            else
            if ( req->method_num == HTTP_PUT )
            {
/**
 * @brief Introduction
 * Modify existing task
 */
                res  = decode_json(req->body);
                if ( !res ) {
                    write(cfd, E_STRL("HTTP/1.1 424 Validation Error\r\nContent-Length:49\r\nContent-Type: application/json;charset=UTF-8"
                                      "\r\nConnection: close\r\n\r\n{\"message\":\"Post body need JSON.\",\"code\":\"false\"}"));
                    goto trash_close;
                }
                blen = *(long *)exjson_get_val_from_key(res, "id");
                if ( !blen ) {
                    write(cfd, E_STRL("HTTP/1.1 424 Validation Error\r\nContent-Length:50\r\nContent-Type: application/json;charset=UTF-8"
                                      "\r\nConnection: close\r\n\r\n{\"message\":\"Need id key parameter\",\"code\":\"false\"}"));
                    goto trash_close;
                }
                rve = exjson_get_val_from_key(res, "data");
                if ( !rve ) {
                    write(cfd, E_STRL("HTTP/1.1 424 Validation Error\r\nContent-Length:48\r\nContent-Type: application/json;charset=UTF-8"
                                      "\r\nConnection: close\r\n\r\n{\"message\":\"Need data parameter\",\"code\":\"false\"}"));
                    goto trash_close;
                }
                
                EXJSON_FOR_EACH_ARRAY(tasks, vv) {
                    if ( e_memcmp(EV_NAME_P(vv), E_STRL("id")) == 0 )
                    {
                        n = *(long *)EV_VALUE_P(vv);
                        if ( n == blen )
                        {
                            bck = node;
                            break;
                        }
                    }
                } EXJSON_FOR_EACH_ARRAY_END();
                
                if ( !bck )
                {
                    write(cfd, E_STRL("HTTP/1.1 404 Not Found\r\nContent-Length:54\r\nContent-Type: application/json;charset=UTF-8\r\n"
                                      "Connection: close\r\n\r\n{\"message\":\"No data with the given id\",\"code\":\"false\"}"));
                    goto trash_close;
                }
/**
 * @brief Introduction
 * Need to be modified
 */
                EXJSON_FOR_EACH_OBJECT(rve, vv) {
                    EXJSON_FOR_EACH_OBJECT(bck, vt) {
                        if ( e_memcmp(EV_NAME_P(vv), E_STRL(EV_NAME_P(vt))) == 0 &&
                            e_memcmp(EV_NAME_P(vt), E_STRL("id")) != 0 )
                        {
                            e_memfree(EV_VALUE_P(vt));
                            if ( EV_TYPE_P(vt) == EXJSON_INT )
                            {
                                EV_VALUE_P(vt) = malloc( sizeof(long) );
                                e_copymem(EV_VALUE_P(vt), EV_VALUE_P(vv), sizeof(long));
                            }
                            else
                            if ( EV_TYPE_P(vt) == EXJSON_STRING )
                            {
                                EV_VALUE_P(vt) = malloc( sizeof(char) * (strlen( EV_VALUE_P( vv ) ) + 1) );
                                e_memzero(EV_VALUE_P(vt), sizeof(char) * (strlen( EV_VALUE_P( vv ) ) + 1));
                                e_copymem(EV_VALUE_P(vt), EV_VALUE_P(vv), strlen( EV_VALUE_P( vv ) ));
                            }
                        }
                    } EXJSON_FOR_EACH_OBJECT_END();
                } EXJSON_FOR_EACH_OBJECT_END();
    
                destroy_exjson(res);
                
                write(cfd, E_STRL("HTTP/1.1 200 OK\r\nContent-Length:61\r\nContent-Type: application/json;charset=UTF-8\r\n"
                                  "Connection: close\r\n\r\n{\"message\":\"Success\",\"code\":\"true\",\"operation\":\"Modify task\"}"));
            }
        }
        else
        {
/**
 * @brief Introduction
 * Other requests response Invalid Request
 */
            write(cfd, E_STRL("HTTP/1.1 404 Not Found\r\nContent-Length:44\r\nContent-Type: application/json;charset=UTF-8\r\n"
                              "Connection: close\r\n\r\n{\"message\":\"Invalid Request\",\"code\":\"false\"}"));
        }
trash_close:
        trash_http_request(req);
        if ( !req->keep_alive )
        {
            close(cfd);
        }
    }
}

void *http_thread(void *d)
{
    int event_fd;
    event_fd  = event_init();
    server_fd = socket_create(NULL, 9898);
    socket_listen(server_fd, 1000);
    event_add(event_fd, server_fd);
    event_loop(event_fd, http_callback);
    return NULL;
}

/**
 * @brief Introduction
 * This thread for task health status check.
 */
void task_thread()
{

}

inline
LIST_DATA *INIT_LIST_DATA()
{
    void *ptr = malloc( sizeof(LIST_DATA ));
    if ( !ptr ) return NULL;
    return ptr;
}

inline
void trash_file_fp(void *v)
{
    if(!v) return ;
    pclose((FILE *)v);
}

inline
void trash_file_exception(void *v)
{
    if ( !v ) return ;
    HTTP_HEADER *t = (HTTP_HEADER *)v;
    e_memfree(HTTP_HEADER_KEY_P(t));
    e_memfree(HTTP_HEADER_VAL_P(t));
    e_memfree(v);
}

inline
void task_fork(char *command)
{
    FILE      *fp;
    LIST_DATA *data;
    size_t     blen;
    char  buff[BUFFER_SIZE] = {0};
    fp = popen(command, "r");
    blen = fread(buff, sizeof(char), sizeof(buff), fp);
    if (blen)
    {
# if 0
        data = INIT_LIST_DATA();
        data->name = malloc( sizeof(char) * (blen + 1) );
        e_memzero(data->name, sizeof(char) * (blen + 1));
        e_copymem(data->name, buff, sizeof(char) * blen);
        
        list_push(tasks_mistake, data, trash_file_exception);
#endif
    }
    list_push(tasks_fp, fp, trash_file_fp);
}

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-variable"
#pragma ide diagnostic ignored "readability-misleading-indentation"

/**
 * @brief Introduction
 * This thread works for Jobs when timer is over.
 */
void main_thread()
{
    long           re;
    char          *conf, *conf_file, *command, *period;
    EXJSON_V      *ev;
    EXJSON        *system_conf;
    struct timeval tv = { .tv_sec = 1, .tv_usec = 0 };
    
/**
 * @brief Introduction
 * The global task
 */
    tasks_fp = INIT_LIST();
    tasks_mistake = INIT_LIST();
    
/**
 * @brief Introduction
 * Read all system config file
 */
    conf  = e_data_from_file("/etc/cknit/cknit.exjson", NULL);
    e_system = decode_json(conf);
    e_memfree(conf);
    
    system_conf = exjson_get_val_from_key(e_system, SYSTEM);
    
/**
 * @brief Introduction
 * Get the task config file and load it to the global memory
 * < tasks >
 */
    conf_file = (char *)exjson_get_val_from_key(system_conf, TASK_CONF);
    conf  = e_data_from_file(conf_file, NULL);
    tasks = decode_json(conf);
    e_memfree(conf);
    
    re = *(long *)exjson_get_val_from_key(system_conf, DAEMON);
    if ( re ) {
        if ( fork() != 0 ) exit(0);
    }
    
/**
 * @brief Introduction
 * Listen the http requests
 */
    pthread_t pid;
    pthread_create(&pid, NULL, http_thread, NULL);
    
    re = 1;
    EXJSON_ARRAY_EACH(tasks, ev) {
/**
 * @brief Introduction
 * Add id for each task
 */
        void *ptr = exjson_get_val_from_key(node, "id");
        if (!ptr) {
            add_object_int( node, "id", re++ );
        }
        ptr = exjson_get_val_from_key(node, "status");
        if ( !ptr ) {
            add_object_int( node, "status", 0);
        }

    } EXJSON_ARRAY_EACH_END();
    
    while ( 1 )
    {
        re = CK_TIMER( &tv );
        assert( re != -1 );
        
/**
 * @brief Introduction
 * Each time, need to loop all tasks, for on-time job.
 */
        command = NULL;
        period  = NULL;
        
        EXJSON_FOR_EACH_ARRAY(tasks, ev) {
            
            if ( e_memcmp(EV_NAME_P(ev), E_STRL(COMMAND)) == 0 )
            {
                command = EV_VALUE_P(ev);
            }
            else
            if ( e_memcmp(EV_NAME_P(ev), E_STRL(STATUS)) == 0 )
            {
                re = *(long *)EV_VALUE_P(ev);
            }
            else
            if ( e_memcmp(EV_NAME_P(ev), E_STRL(PERIOD)) == 0 )
            {
                period = EV_VALUE_P(ev);
            }
                
            if ( period && e_match_time(period) && command && !re )
            {
                task_fork(command);
            }
        } EXJSON_FOR_EACH_ARRAY_END();
    }
}

#pragma clang diagnostic pop